﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a file owner.
    /// </summary>
    public class CreateFileOwner : CreateAccountProfile<FileOwnerEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FILESYS");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .Rule((ctx, uow) => ctx.Account(uow).EntityID == AccountID);
            }
        }

        /// <summary>
        /// Gets or sets the file quota.
        /// </summary>
        public long Quota
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public FileOwnerFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(Quota < 0)
                return Err.Of<FileOwnerEntity>().NotValid("Quota");
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out FileOwnerEntity result)
        {
            base.Work(uow, out result);
            try
            {
                uow.Execute(new EnqueueFileOwnerNotification
                {
                    OwnerID = result.EntityID,
                    RecipientID = result.Account.EntityID
                });
            }
            catch(Exception ex)
            {
                uow.Execute(new CreateLogRecordFromException
                {
                    Ex = ex
                });
            }
        }

        protected override void Construct(IUnitOfWork uow, FileOwnerEntity entity)
        {
            base.Construct(uow, entity);

            entity.Quota = Quota;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates a file owner.
    /// </summary>
    public class UpdateFileOwner : UpdateAccountProfile<FileOwnerEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FILESYS");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .Rule((ctx, uow) =>
                    {
                        var owner = ctx.FileOwner(uow);
                        return owner != null && owner.EntityID == EntityID;
                    });
            }
        }

        /// <summary>
        /// Gets or sets the file quota.
        /// </summary>
        public long Quota
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public FileOwnerFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(Quota < 0)
                return Err.Of<FileOwnerEntity>().NotValid("Quota");
            return base.Validate(uow);
        }

        protected override void Revise(IUnitOfWork uow, FileOwnerEntity entity)
        {
            base.Revise(uow, entity);

            entity.Quota = Quota;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates a file owner statistics.
    /// </summary>
    public class UpdateFileOwnerStats : OperateEntityByID<FileOwnerEntity>
    {
        #region Utilities

        protected override void Work(IUnitOfWork uow, out FileOwnerEntity result)
        {
            var owner = uow.Execute(new GetFileOwner
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;

            uow.Flush();
            uow.SqlQuery("exec fwk_FileOwners_UpdateStats @OwnerID=:OwnerID, @UpdatedOn=:UpdatedOn")
                .SetInt32("OwnerID", owner.EntityID)
                .SetDateTime("UpdatedOn", DateTime.UtcNow)
                .ExecuteUpdate();
            uow.Flush();

            result = uow.Execute(new RefreshEntity<FileOwnerEntity>
            {
                Entity = owner
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the file owner.
    /// </summary>
    public class DeleteFileOwner : DeleteAccountProfile<FileOwnerEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FILESYS");
            }
        }

        #endregion
    }

    /// <summary>
    /// Gets the file owner.
    /// </summary>
    public class GetFileOwner : GetAccountProfile<FileOwnerEntity>,
        IApiOperation
    {
    }

    /// <summary>
    /// Gets the file owner.
    /// </summary>
    public class GetFileOwnerByAccount : GetAccountProfileByAccount<FileOwnerEntity>,
        IApiOperation
    {
        #region Options

        protected override bool Cacheable
        {
            get
            {
                return true;
            }
        }

        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over file owner collection.
    /// </summary>
    public class SearchFileOwners : SearchOperation<FileOwnerEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchFileOwners()
        {
            Term = SearchTerm.Empty;
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                FileOwnerSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public FileOwnerFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public FileOwnerSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<FileOwnerEntity> Build(IUnitOfWork uow, IQueryable<FileOwnerEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.Account.Email.StartsWith(Term.Keyword) ||
                        x.Account.Username.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.Account.Email.EndsWith(Term.Keyword) ||
                        x.Account.Username.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.Account.Email.Contains(Term.Keyword) ||
                        x.Account.Username.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.Account.Email.Equals(Term.Keyword) ||
                        x.Account.Username.Equals(Term.Keyword));
                    break;
            }
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case FileOwnerSortBy.AccountEmail:
                            query = query.OrderBy(x => x.Account.Email, SortDir, i != 0);
                            break;
                        case FileOwnerSortBy.AccountUsername:
                            query = query.OrderBy(x => x.Account.Username, SortDir, i != 0);
                            break;
                        case FileOwnerSortBy.Quota:
                            query = query.OrderBy(x => x.Quota, SortDir, i != 0);
                            break;
                        case FileOwnerSortBy.QuotaUsage:
                            query = query.OrderBy(x => x.QuotaUsage, SortDir, i != 0);
                            break;
                        case FileOwnerSortBy.FileCount:
                            query = query.OrderBy(x => x.FileCount, SortDir, i != 0);
                            break;
                        case FileOwnerSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    /// <summary>
    /// Gets the collection of file owners as a dictionary, where 
    /// key - file owner identifier and value - account email.
    /// </summary>
    public class GetFileOwnerDictionary : SearchOperation<FileOwnerEntity, Int32, String>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public GetFileOwnerDictionary()
        {
            Term = SearchTerm.Empty;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public FileOwnerFlags FlagMask
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<FileOwnerEntity> Build(IUnitOfWork uow, IQueryable<FileOwnerEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x =>
                        x.Account.Email.StartsWith(Term.Keyword) ||
                        x.Account.Username.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x =>
                        x.Account.Email.EndsWith(Term.Keyword) ||
                        x.Account.Username.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x =>
                        x.Account.Email.Contains(Term.Keyword) ||
                        x.Account.Username.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x =>
                        x.Account.Email.Equals(Term.Keyword) ||
                        x.Account.Username.Equals(Term.Keyword));
                    break;
            }
            query = query.OrderBy(x => x.Account.Email);
            return query;
        }

        protected override IQueryable<KeyValuePair<Int32, String>> Map(IQueryable<FileOwnerEntity> query)
        {
            return query.Select(x => new KeyValuePair<Int32, String>(x.EntityID, x.Account.Email));
        }

        #endregion
    }

    #endregion

    #region __Notification__

    /// <summary>
    /// Enqueues a file owner notification message.
    /// </summary>
    /// <remarks>
    /// Message template code: IFSOWNR
    /// Allowed tokens: [T:Recipient.EntityID], [T:Recipient.Guid], [T:Recipient.Email], [T:Recipient.Username],
    /// [T:FileOwner.Account.EntityID], [T:FileOwner.Account.Guid], [T:FileOwner.Account.Email],
    /// [T:FileOwner.Account.Username], [T:FileOwner.EntityID], [T:FileOwner.Quota], [T:FileOwner.QuotaRemainder],
    /// [T:FileOwner.QuotaUsage]
    /// </remarks>
    public class EnqueueFileOwnerNotification : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the owner identifier.
        /// </summary>
        public int OwnerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient account identifier.
        /// </summary>
        public int RecipientID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var owner = uow.Execute(new GetFileOwner
            {
                ThrowsException = true,
                EntityID = OwnerID
            }).Result;
            var recipient = owner.Account.EntityID == RecipientID ? owner.Account : uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = RecipientID
            }).Result;
            var sysLocale = uow.Execute(new GetLocalizationProfileByCode
            {
                ThrowsException = true,
                Code = uow.Execute(new GetConfigParamValueByKey
                {
                    Key = Par.In("Core").Of<LocalizationProfileEntity>().Name("System")
                }).Result
            }).Result;
            var usrLocale = recipient.LocalizationProfile;
            var numFmt = usrLocale.Country.NumericFormat;
            decimal quota = MathHelper.Ratio(owner.Quota, sysLocale.InformationUnit.Ratio, usrLocale.InformationUnit.Ratio);
            decimal quotaRemainder = MathHelper.Ratio(owner.QuotaRemainder, sysLocale.InformationUnit.Ratio, usrLocale.InformationUnit.Ratio);
            decimal quotaUsage = MathHelper.Ratio(owner.QuotaUsage, sysLocale.InformationUnit.Ratio, usrLocale.InformationUnit.Ratio);
            string quotaStr = String.Format(usrLocale.InformationUnit.ValueFormat, numFmt.Convert(quota));
            string quotaRemainderStr = String.Format(usrLocale.InformationUnit.ValueFormat, numFmt.Convert(quotaRemainder));
            string quotaUsageStr = String.Format(usrLocale.InformationUnit.ValueFormat, numFmt.Convert(quotaUsage));

            result = uow.Execute(new CreateTemplatedMessageByCode
            {
                TemplateCode = "IFSOWNR",
                LanguageID = usrLocale.Language.EntityID,
                Replacements = new Dictionary<String, String>
                {
                    {"Recipient.EntityID", recipient.EntityID.ToString()},
                    {"Recipient.Email", recipient.Email},
                    {"Recipient.Username", recipient.Username},
                    {"Recipient.Guid", recipient.Guid.ToString()},
                    {"FileOwner.Account.EntityID", owner.Account.EntityID.ToString()},
                    {"FileOwner.Account.Email", owner.Account.Email},
                    {"FileOwner.Account.Username", owner.Account.Username},
                    {"FileOwner.Account.Guid", owner.Account.Guid.ToString()},
                    {"FileOwner.EntityID", owner.EntityID.ToString()},
                    {"FileOwner.Quota", quotaStr},
                    {"FileOwner.QuotaRemainder", quotaRemainderStr},
                    {"FileOwner.QuotaUsage", quotaUsageStr}
                },
                To = recipient.Email,
                Flags = MessageFlags.Active
            }).Result;
        }

        #endregion
    }

    #endregion
}
